//
//  PeerTalkHelper.swift
//  PeerTalkPlaygroundWithSPM
//
//  Created by treastrain on 2021/05/15.
//

import Foundation
import PeerTalk
import SwiftUI

enum Settings {
    static let port: in_port_t = 50621
    static let frameType: UInt32 = 1
    
#if os(macOS)
    static let reconnectDelay: TimeInterval = 1.0
    static let notConnectedQueue = DispatchQueue(label: "jp.tret.PeerTalkPlaygroundWithSPM.notConnectedQueue")
#endif
}



class PeerTalkHelper: NSObject, ObservableObject {
    
    //@Published var messages: [String] = []
    
    @Published var text = ""
    
    @Published var order = "1"
    
    @Published var isSendingToElefante: Bool = false
    var peerChannel: PTChannel?
    
#if os(iOS) || os(tvOS)
    var serverChannel: PTChannel?
#endif
    
#if os(macOS)
    var connectingToDeviceID: NSNumber?
    var connectedDeviceID: NSNumber?
    var connectedDeviceProperties: [String : Any] = [:]
#endif
    
    // MARK: MULTIPLE DEVICES
    
    @Published var devices: [UsbmuxdDevice] = [] 
    
    func getDeviceByAlias(alias: String) ->  [UsbmuxdDevice] {
        let matchedDevice = self.devices.filter { device in
            device.alias == alias
        }
        print(devices.count)
        print(devices.first?.alias)
        ELog.d(String(describing: type(of: self)),#function, "matchedDevice: \(matchedDevice)", #line)
        PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "matchedDevice: \(matchedDevice)")
        return matchedDevice
    }
    
    func startListening() {
        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(forName: .deviceDidAttach, object: PTUSBHub.shared(), queue: nil) { [self] notification in
            let deviceID = notification.userInfo?["DeviceID"] as! NSNumber
            ELog.d(String(describing: type(of: self)),#function, "PTUSBDeviceDidAttachNotification: \(deviceID)", #line)
            PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "PTUSBDeviceDidAttachNotification: \(deviceID)")
            UsbmuxdDevice.notConnectedQueue.async { [self] in
                let isExist = devices.filter { device in
                    device.deviceID == deviceID
                }
                if (isExist.count > 0) {
                    ELog.d(String(describing: type(of: self)),#function, "Device already registered", #line)
                } else {
                    UsbmuxdDevice.devicesModifyQueue.sync {
                        let device = UsbmuxdDevice(deviceID: deviceID)
                        device.connectedDeviceProperties = notification.userInfo?["Properties"] as? [String : Any] ?? [:]
                        ///   UsbmuxdDevice.notConnectedQueue.async {
                        //      DispatchQueue.main.async { [self] in
                        connectToClient(device: device)
                        enqueueConnectToLocalIPv4PortMulti()
                        devices.append(device)
                    }
                    //    }
                    //   }
                    //
                }
            }
        }
        
        notificationCenter.addObserver(forName: .deviceDidDetach, object: PTUSBHub.shared(), queue: nil) { [self] notification in
            let deviceID = notification.userInfo?["DeviceID"] as! NSNumber
            ELog.d(String(describing: type(of: self)),#function, "PTUSBDeviceDidDetachNotification: \(deviceID)", #line)
            PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "PTUSBDeviceDidAttachNotification: \(deviceID)")
            UsbmuxdDevice.devicesModifyQueue.sync {
                var dCount = 0
                for device in devices {
                    if (device.deviceID == deviceID) {
                        ELog.d(String(describing: type(of: self)),#function, "✅ Removed device #\(device.deviceID)\n\(device.connectedDeviceProperties)", #line)
                        PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "✅ Removed device #\(device.deviceID)\n\(device.connectedDeviceProperties)")
                        break
                    }
                    dCount += 1
                }
                
                if devices.count > 0 {
                    devices[dCount].channel?.close()
                    devices.remove(at: dCount)
                }
                for device in devices {
                    ELog.d(String(describing: type(of: self)),#function, "deviceIds \(device.deviceID)", #line)
                }
            }
        }
    }
    
    func connectToClient(device: UsbmuxdDevice) {
        device.channel = PTChannel(protocol: nil, delegate: self)
        device.channel!.userInfo = device.deviceID as Any
        device.channel!.connect(to: Int32(UsbmuxdDevice.port), over:  PTUSBHub.shared(), deviceID: device.deviceID) { error in
            if let error = error as NSError? {
                if error.domain == PTUSBHubErrorDomain, error.code == PTUSBHubErrorConnectionRefused.rawValue {
                    // self.addMessage("❌ Failed to connect to device #\(channel.userInfo): \(error)")
                    ELog.d(String(describing: type(of: self)),#function,"❌ Failed to connect to device: \(error)", #line)
                    PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "❌ Failed to connect to device #\(device.channel!.userInfo): \(error)")
                } else {
                    ELog.d(String(describing: type(of: self)),#function, "❌ Failed to connect to device #\(device.channel!.userInfo): \(error)", #line)
                    PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "❌ Failed to connect to device #\(device.channel!.userInfo): \(error)")
                }
                if device.channel!.userInfo as? NSNumber == device.deviceID {
                    ELog.d(String(describing: type(of: self)),#function, "❌ Failed to connect to device #\(device.channel!.userInfo): \(error)", #line)
                    PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "❌ Failed to connect to device #\(device.channel!.userInfo): \(error)")
                }
            } else {
                //   self.connectedDeviceID = deviceID
                //    self.peerChannel = channel
                ELog.d(String(describing: type(of: self)),#function, "✅ Connected to device #\(device.deviceID)\n\(device.connectedDeviceProperties)", #line)
                PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "✅ Connected to device #\(device.deviceID)\n\(device.connectedDeviceProperties)")
            }
        }
    }
    
    
    override init() {
        super.init()
        
#if os(iOS) || os(tvOS)
        self.setupClientSideChannel()
#elseif os(macOS)
        // Start listening for device attached/detached notifications
        self.startListeningForDevices()
        // Start trying to connect to local IPv4 port
        self.enqueueConnectToLocalIPv4Port()
        ELog.d(String(describing: type(of: self)),#function, "⌛ Ready for action — connecting at will.", #line)
#endif
    }
    
    
    init(a: Int) {
        super.init()
        
#if os(iOS) || os(tvOS)
        self.setupClientSideChannel()
#elseif os(macOS)
        // Start listening for device attached/detached notifications
        self.startListening()
        // Start trying to connect to local IPv4 port
        self.enqueueConnectToLocalIPv4Port()
        ELog.d(String(describing: type(of: self)),#function, "⌛ Ready for action — connecting at will.", #line)
#endif
    }
    
    deinit {
#if os(iOS) || os(tvOS)
        if self.serverChannel != nil {
            self.serverChannel?.close()
        }
#endif
    }
    
    
    func sendMessage() {
        guard !self.text.isEmpty else {
            return
        }
        // print(#function, self.text, Date())
        
#if os(iOS) || os(tvOS)
        addMessage("📱 \(self.text)")
#elseif os(macOS)
        ELog.d(String(describing: type(of: self)),#function, "💻 \(self.text)", #line)
#else
        addMessage("❓ \(self.text)")
#endif
        send(self.text)
    }
    
    func sendMessage(message: String, device: UsbmuxdDevice) {
        guard !message.isEmpty else {
            return
        }
        send(message, device: device)
    }
    
    func sendMessage(device: UsbmuxdDevice) {
        guard !self.text.isEmpty else {
            return
        }
        
        //    print(#function, self.text, Date())
        
#if os(iOS) || os(tvOS)
        addMessage("📱 \(self.text)")
#elseif os(macOS)
        //     addMessage("💻 To device: \(device.deviceID)")
        //      addMessage("💻 \(self.text)")
#else
        addMessage("❓ \(self.text)")
#endif
        
        send(self.text, device: device)
    }
  
    
    private func send(_ message: String, device: UsbmuxdDevice) {
        guard let channel = device.channel else {
            ELog.d(String(describing: type(of: self)),#function, "❌ Can not send the message - not connected: \"\(message)\"", #line)
            PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "❌ Can not send the message - not connected: \"\(message)\"")
            return
        }
        var message = message
        let payload = message.withUTF8 { buffer -> Data in
            var data = Data()
            data.append(CFSwapInt32HostToBig(UInt32(buffer.count)).data)
            data.append(buffer)
            return data
        }
        channel.sendFrame(type: Settings.frameType, tag: PTFrameNoTag, payload: payload) { error in
            if let error = error {
                ELog.d(String(describing: type(of: self)),#function, "❌ Can not send the message - error: \(error) \"\(message)\"", #line)
                PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "❌ Can not send the message - error: \(error) \"\(message)\"")
            }
        }
    }
    
    private func send(_ message: String) {
        guard let channel = self.peerChannel else {
            ELog.d(String(describing: type(of: self)),#function, "❌ Can not send the message - not connected: \"\(message)\"", #line)
            PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "❌ Can not send the message - not connected: \"\(message)\"")
            return
        }
        var message = message
        let payload = message.withUTF8 { buffer -> Data in
            var data = Data()
            data.append(CFSwapInt32HostToBig(UInt32(buffer.count)).data)
            data.append(buffer)
            return data
        }
        channel.sendFrame(type: Settings.frameType, tag: PTFrameNoTag, payload: payload) { error in
            if let error = error {
                ELog.d(String(describing: type(of: self)),#function, "❌ Can not send the message - error: \(error) \"\(message)\"", #line)
                PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "❌ Can not send the message - error: \(error) \"\(message)\"")
            }
        }
    }
    
    
//    func addMessage(_ message: String) {
//        self.messages.insert(message, at: 0)
//        if (messages.count > 100) {
//            messages.removeLast()
//        }
//    }
}

extension FixedWidthInteger {
    var data: Data {
        var bytes = self
        return Data(bytes: &bytes, count: MemoryLayout.size(ofValue: self))
    }
}
